import 'dart:io';

import 'package:dart_bean/generator.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:toml/toml.dart';

part 'config.g.dart';

///support both global & project config
@JsonSerializable()
class Config {
  /// the name of dir stored all generated files
  @JsonKey(defaultValue: 'gen')
  late String genDirName;

  ///full file path or not in console log<br/>
  /// 'my_project/lib/bean.dart' <- clickable then navigate to source code <br/>
  /// 'bean.dart' <- not clickable
  @JsonKey(defaultValue: false)
  late bool clickableFileLink;

  ///OfficialBean: file.contains("part 'file_name.g.dart';") <br/>
  ///use json_serializable for unsupported feature of dart_bean
  @JsonKey(defaultValue: true)
  late bool ignoreOfficialBean;

  ///when json2bean, no prefix(beanName) for Json child bean or not<br/>
  @JsonKey(defaultValue: true)
  late bool flattenBean;

  ///support parse null json to default value or not. <br/>
  ///eg: late int a; when null json, a=0;
  @JsonKey(defaultValue: false)
  late bool autoDefaultValueStyle;

  @JsonKey(defaultValue: 'json_support.dart')
  late String jsonSupportFileName;

  @JsonKey(defaultValue: 'json_cast.dart')
  late String jsonCastFileName;

  @JsonKey(defaultValue: r'$JSON')
  late String jsonCastParamName;

  @JsonKey(defaultValue: 'json')
  late String fromJsonParamName;

  @JsonKey(defaultValue: 'it')
  late String toJsonParamName;

  ///fromJson method style 'static' or 'factory'
  @JsonKey(defaultValue: 'static')
  late String fromJsonType;

  ///line width for generated files
  @JsonKey(defaultValue: 160)
  late int dartFormatterPageWidth;

  late final bool isStaticFromJson = fromJsonType == 'static';

  static Config fromJson(dynamic json) => _$ConfigFromJson(json);

  Map<String, dynamic> toJson() => _$ConfigToJson(this);
}

Config parseConfig(Directory projectDir) {
  try {
    return Generator.config;
  } catch (e) {
    final file = File('${projectDir.path}/.json_bean.toml');
    return _readConfig(file.path);
  }
}

Config _readConfig(String path) {
  final global = _getConfigMap(_getGlobalJsonBeanConfigPath());
  final map = _getConfigMap(path) ?? {};
  if (global != null) {
    global.forEach((key, value) {
      if (!map.containsKey(key)) map[key] = value;
    });
  }
  final config = Config.fromJson(map);
  final file = File(path);
  if (!file.existsSync() || file.lengthSync() < 3) {
    file.writeAsStringSync(_tomlEncode(config.toJson(), global != null));
  }
  return config;
}

String? _getGlobalJsonBeanConfigPath() {
  Map<String, String> envVars = Platform.environment;
  return envVars['JSON_BEAN_CONFIG']; //'xx_json_bean.toml';
}

Map<String, dynamic>? _getConfigMap(String? path) {
  if (path == null) return null;
  final file = File(path);
  if (file.existsSync()) {
    final content = File(file.path).readAsStringSync();
    try {
      return TomlDocument.parse(content).toMap();
    } catch (e) {
      onError('${file.path}\nconfig not work for error, please fix or delete to auto create default one');
      return null;
    }
  }
  return null;
}

String _tomlEncode(Map<String, dynamic> map, bool asComment) {
  final sb = StringBuffer();
  sb.writeln('#TOML https://toml.io/cn/\n');
  map.forEach((key, value) {
    if (asComment) {
      sb.write("#$key = ");
    } else {
      sb.write("$key = ");
    }

    if (value is String) {
      sb.write("'$value'");
    } else {
      sb.write(value);
    }
    sb.writeln();
  });
  return sb.toString();
}
